angular.module('ui.dateparser', [])
    .service('dateParser', ['$locale', 'orderByFilter', function ($locale, orderByFilter) {
        this.parsers = {};
        var formatCodeToRegex = {
            'yyyy': {
                regex: '\\d{4}',
                apply: function (value) {
                    this.year = +value;
                }
            },
            'yy': {
                regex: '\\d{2}',
                apply: function (value) {
                    this.year = +value + 2000;
                }
            },
            'y': {
                regex: '\\d{1,4}',
                apply: function (value) {
                    this.year = +value;
                }
            },
            'MMMM': {
                regex: $locale.DATETIME_FORMATS.MONTH.join('|'),
                apply: function (value) {
                    this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value);
                }
            },
            'MMM': {
                regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'),
                apply: function (value) {
                    this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value);
                }
            },
            'MM': {
                regex: '0[1-9]|1[0-2]',
                apply: function (value) {
                    this.month = value - 1;
                }
            },
            'M': {
                regex: '[1-9]|1[0-2]',
                apply: function (value) {
                    this.month = value - 1;
                }
            },
            'dd': {
                regex: '[0-2][0-9]{1}|3[0-1]{1}',
                apply: function (value) {
                    this.date = +value;
                }
            },
            'd': {
                regex: '[1-2]?[0-9]{1}|3[0-1]{1}',
                apply: function (value) {
                    this.date = +value;
                }
            },
            'EEEE': {
                regex: $locale.DATETIME_FORMATS.DAY.join('|')
            },
            'EEE': {
                regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|')
            }
        };
        this.createParser = function (format) {
            var map = [], regex = format.split('');
            angular.forEach(formatCodeToRegex, function (data, code) {
                var index = format.indexOf(code);
                if (index > -1) {
                    format = format.split('');
                    regex[index] = '(' + data.regex + ')';
                    format[index] = '$'; // Custom symbol to define consumed part of format
                    for (var i = index + 1, n = index + code.length; i < n; i++) {
                        regex[i] = '';
                        format[i] = '$';
                    }
                    format = format.join('');
                    map.push({ index: index, apply: data.apply });
                }
            });
            return {
                regex: new RegExp('^' + regex.join('') + '$'),
                map: orderByFilter(map, 'index')
            };
        };
        this.parse = function (input, format) {
            if (!angular.isString(input)) {
                return input;
            }
            format = $locale.DATETIME_FORMATS[format] || format;
            if (!this.parsers[format]) {
                this.parsers[format] = this.createParser(format);
            }
            var parser = this.parsers[format],
                regex = parser.regex,
                map = parser.map,
                results = input.match(regex);
            if (results && results.length) {
                var fields = { year: 1900, month: 0, date: 1, hours: 0 }, dt;
                for (var i = 1, n = results.length; i < n; i++) {
                    var mapper = map[i - 1];
                    if (mapper.apply) {
                        mapper.apply.call(fields, results[i]);
                    }
                }
                if (isValid(fields.year, fields.month, fields.date)) {
                    dt = new Date(fields.year, fields.month, fields.date, fields.hours);
                }
                return dt;
            }
        };
        // Check if date is valid for specific month (and year for February).
        // Month: 0 = Jan, 1 = Feb, etc
        function isValid(year, month, date) {
            if (month === 1 && date > 28) {
                return date === 29 && ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0);
            }
            if (month === 3 || month === 5 || month === 8 || month === 10) {
                return date < 31;
            }
            return true;
        }
    }]);